﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Await_Async
    {
        public void Await_Async_Test()
        {            
            //Func1();  //简单调用
            //Func2();  //调用带参方法和Task调用带返回值的方法

            //Func3();  //线程冲突
            //Func4();  //用独占锁解决冲突
            //Func5();  //用信号量解决冲突,可以允许多个线程同时访问，处理效率会高些
            //Func6();  //线程中的异常捕获

            //Func7();  //await和async的执行顺序
            //Func8();  //await的执行顺序
            //Func9();  //await与task的关系
            Func10();

        }
        #region 多线程调用方法
        
        
        //简单的创建线程
        private void Func1()
        { 
            //Thread,Task
            new Thread(Go).Start();     //.net 1.0的线程，创建后需要手动调用Start()方法启动线程
            Task.Factory.StartNew(Go);  //4.0引入的Task   Task的StartNew和Run会创建线程并立即启动
            Task.Run(new Action(Go));   //4.5新增的Run方法

        }
        private void Go()
        {
            Console.WriteLine("我是另外一个线程,ID:{0}",Thread.CurrentThread.ManagedThreadId);
        }

        //多线程调用带参方法
        private void Func2()
        {
            //注意Thread的方式需要调用Start()启动线程
            new Thread(Go2).Start("aaaaa");                                 //不用匿名委托，只能传一个object参数
            new Thread(delegate() { GoGo2("aaa", "bbb", "ccc"); }).Start(); //用匿名委托可以任意传参
            new Thread(() => { GoGo2("123", "123", "321"); }).Start();      //Lambda也是同样效果
            //Task是自动启动的
            Task.Run(() => { GoGo2("brrf", "awef", "fff"); });              //Task也是用了Lambda
            //Thread不能调用带返回值的方法，Task可以,返回一个TResult
            var taskResult = Task.Run<string>(() => { return "This is Task Result!"; });
            Console.WriteLine(taskResult.Result);
        }
        private void Go2(object data)
        {
            Console.WriteLine("data:{0},ThreadID:{1}",data,Thread.CurrentThread.ManagedThreadId);
        }
        private void GoGo2(string args1,string args2,string args3)
        {            
            Console.WriteLine("data:{0} {1} {2},ThreadID:{3}",args1,args2,args3,Thread.CurrentThread.ManagedThreadId);
        }

        #endregion

        #region 线程安全
        private static bool isDone = false;
        //线程安全
        private void Func3()
        {
            new Thread(Done).Start();
            new Thread(Done).Start();
        }
        private void Done()
        {
            //偶尔会出现两次Done，线程1还没设置成true的时候线程2也进入了，造成并发的异常结果
            if (!isDone)
            {
                Console.WriteLine("Done!");
                isDone = true;
            }
        }

        //用独占锁解决线程冲突的问题
        private void Func4()
        {
            new Thread(Done2).Start();
            new Thread(Done2).Start();
        }        
        private static readonly object syncLock = new object();//锁要是静态的只读的
        private void Done2()
        {
            //被锁住的代码在同一个时间内只允许一个线程访问，
            //其它的线程会被阻塞，只有等到这个锁被释放之后其它的线程才能执行被锁住的代码。
            lock (syncLock)
            {
                if (!isDone)
                {
                    Console.WriteLine("Done2!");
                    isDone = true;
                }
            }           
        }

        //信号量
        private void Func5()
        {
            for (int i = 0; i < 5; i++)
            {
                new Thread(Done3).Start(i);
            }
        }
        private static SemaphoreSlim sem = new SemaphoreSlim(3);    //信号量，和锁功能类似，但是允许一定数量的线程同时访问
        private void Done3(object id)
        {
            //前3个线程立刻就能进入执行状态，后面2个就要等前面的线程离开了才行
            Console.WriteLine(id+"开始排队...");
            sem.Wait();
            Console.WriteLine(id+"开始处理！");
            Thread.Sleep(1000*(int)id);
            Console.WriteLine(id + "处理完毕，离开！");
            sem.Release();
        }

        //异常处理
        private void Func6()
        {
            //线程
            //try
            //{
            //    new Thread(ThrowEx).Start();
            //}
            //catch (Exception ex)    //捕获不到其他线程里发生的异常
            //{
            //    Console.WriteLine(ex.Message);
            //}

            //task (目前测试下来并没有捕获，需要后续研究15.7.13)
            try
            {
                //var task = Task.Run(() => { ThrowEx(); });
                //task.Wait();    //一定要调用了Wait方法，主线程才能捕获task里的异常
                //有返回值的Task只要调用了task.Result()方法就能捕获异常了
                var task2 = Task.Run(() => { return ThrowEx2(); });
                var name = task2.Result;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        private void ThrowEx() { throw null; }
        private string ThrowEx2() { throw null; }

        #endregion

        #region await和async的机制
        private void Func7()
        {
            //执行顺序 1.AsyncTest1()  
            //2.遇到task.run或task.factory.startnew时开启新线程
            //3.执行主线程的cw  
            //4.AsyncTest1()中的事件            
            Test();
            Console.WriteLine("CurrentThreadID:{0}",Thread.CurrentThread.ManagedThreadId);
        }
        private async Task Test()
        { 
            //将方法标记为async，就可以用await调用同样标为async的方法
            //await后面的方法将在另外一个线程中执行
            await AsyncTest1();
        }
        private async Task AsyncTest1()
        {
            await Task.Delay(1000); //.net 4.5后才有delay方法

            Console.WriteLine("ThreadID:{0}",Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("In other thread...");
        }

        private void Func8()
        {
            /*
             * await 不会开启新的线程，当前线程会一直往下走直到遇到真正的Async方法（比如说HttpClient.GetStringAsync），
             * 这个方法的内部会用Task.Run或者Task.Factory.StartNew 去开启线程。
             * 也就是如果方法不是.NET为我们提供的Async方法，我们需要自己创建Task，才会真正的去创建线程。
             */
            Console.WriteLine("Main Thread:{0}",Thread.CurrentThread.ManagedThreadId);
            Test2();
        }
        private async Task Test2()
        {
            Console.WriteLine("Before Calling GetName,ThreadID:{0}",Thread.CurrentThread.ManagedThreadId);
            var name = GetName2();  //进入GetName2后，一直到Task.Run才会开启新线程,如果await加在这里，下面的就都要等待
            Console.WriteLine("End calling GetName.");  //开启新线程后，主线程不会挂起，继续执行
            Console.WriteLine("Get result from GetName:{0}",await name);    //这里需要等待结果，如果没完成主线程就会挂起
            Console.WriteLine("abbbb");     //获取到结果后才能继续执行

        }
        private async Task<string> GetName2()
        { 
            //这里还是主线程
            Console.WriteLine("Before calling Task.Run,current threadID is:{0}",Thread.CurrentThread.ManagedThreadId);
            return await Task.Run(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("'GetName' threadID is:{0}", Thread.CurrentThread.ManagedThreadId);
                return "Jessy";
            });
        }

        private void Func9()
        {
            //async方法在调用前一定要加await？
            Test3();
        }
        private async void Test3()
        {
            Task<string> task = Task.Run(() => {
                Thread.Sleep(2000);
                return "Hello World";
            });

            //等待2秒后才接受到结果
            //说明await并不是针对于async的方法，而是针对async方法所返回给我们的Task
            //这也是为什么所有的async方法都必须返回给我们Task
            string name = await task;
            Console.WriteLine(name);
        }

        private void Func10()
        {
            //不使用await，确认Task的完成状态
            /*加上await关键字之后，后面的代码会被挂起等待，直到task执行完毕有返回值的时候才会继续向下执行，
             * 这一段时间主线程会处于挂起状态。GetAwaiter方法会返回一个awaitable的对象
             * （继承了INotifyCompletion.OnCompleted方法）我们只是传递了一个委托进去，
             * 等task完成了就会执行这个委托，但是并不会影响主线程，下面的代码会立即执行。
             * 这也是为什么我们结果里面第一句话会是 “主线程执行完毕”！
            */
            var task = Task.Run(() => {
                return GetName4();
            });

            task.GetAwaiter().OnCompleted(() => {
                //2秒之后才会执行这里
                var name = task.Result;
                Console.WriteLine("My name is:{0}",name);
            });

            //Task.GetAwait()方法会给我们返回一个awaitable的对象，通过调用这个对象的GetResult方法就会挂起主线程
            //await 实质是在调用awaitable对象的GetResult方法
            var name2 = task.GetAwaiter().GetResult();            
            Console.WriteLine("name:{0}",name2);

            Console.WriteLine("主线程执行完毕");
        }
        private string GetName4()
        {
            Console.WriteLine("另外一个线程在获取名称");  
            Thread.Sleep(2000);
            return "Jay";
        }

        #endregion

    }
}
